/**
 * Collaborative Story Development Web Application (CSDApp) Copyright Anton
 * Strack 2014
 *
 * This file is part of Collaborative Story Development Application (CSDApp).
 *
 * CSDApp is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * CSDApp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * CSDApp. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package csdwa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;

/**
 * This class acts as a wrapper/adapter for and between HttpServletRequest and
 * the controller classes. It standardizes some basic actions and is intended to
 * be passed between all participating controllers in the request cycle.
 * Controller classes should use this object to interact with the
 * HttpServletRequest object. This class maintains the state of the
 * HttpServletRequest object and so anytime the HttpServletRequest object is
 * retrieved, it should be up to date according to the request.
 *
 * @author Anton Strack
 */
public class Request implements CsdwaRequest {

    private HttpServletRequest request;
    private String currentRequestType;
    private String formId;
    private String referer;
    private Pattern actionPattern;
    private Pattern resourcePattern;
    private Pattern idPattern;
    private Pattern secondaryResourcePattern;
    private RequestType requestType;

    /**
     * Constructs the request object with the primary values it extracts from
     * the request object.
     *
     * @param request the request object that is responsible for storing and
     * maintaining the state of the request.
     */
    public Request(HttpServletRequest request) {
        this(request, request.getParameter("action"), request.getParameter("resource"), request.getParameter("id"));
    }

    /**
     * Constructs the request object with the primary values equalling the three
     * passed in values. This constructor will not attempt to retrieve the
     * primary values from the request object. Defaults to the synchronous
     * request type.
     *
     * @param request the request object that is responsible for storing and
     * maintaining the state of the request.
     * @param primaryActionTypeName name of the action type such as update or
     * add
     * @param primaryResourceTypeName name of the resource type such as plot or
     * comment
     * @param primaryResourceId id of a specific resource that corresponds to
     * the action is to be preformed on.
     */
    public Request(HttpServletRequest request, String primaryActionTypeName, String primaryResourceTypeName, String primaryResourceId) {
        this(request, primaryActionTypeName, primaryResourceTypeName, primaryResourceId, RequestType.SYNC);
    }

    /**
     * Constructs the request object with the primary values equalling the three
     * passed in values. This constructor will not attempt to retrieve the
     * primary values from the request object.
     *
     * @param request the request object that is responsible for storing and
     * maintaining the state of the request.
     * @param primaryActionTypeName name of the action type such as update or
     * add
     * @param primaryResourceTypeName name of the resource type such as plot or
     * comment
     * @param primaryResourceId id of a specific resource that corresponds to
     * the action is to be preformed on.
     * @param requestType the type of request. SYNC or ASYNC
     */
    public Request(HttpServletRequest request, String primaryActionTypeName, String primaryResourceTypeName, String primaryResourceId, RequestType requestType) {
        this.request = request;
        int id = 0;
        if (primaryResourceId != null && primaryResourceId.isEmpty() == false) {
            id = Integer.parseInt(primaryResourceId);
        }
        if (primaryActionTypeName == null || primaryActionTypeName.isEmpty()) {
            primaryActionTypeName = "view";
        }
        if (primaryResourceTypeName == null || primaryResourceTypeName.isEmpty()) {
            primaryResourceTypeName = "site";
        }
        this.setPrimaryResourceTypeName(primaryResourceTypeName.toLowerCase());
        this.setPrimaryResourceId(id);
        this.setPrimaryActionTypeName(primaryActionTypeName.toLowerCase());
        this.setFormId(request.getParameter("formSubmit"));
        this.setSecondaryResourceTypeName(request.getParameter("for"));
        this.setSecondaryActionTypeName(request.getParameter("and"));
        this.setSecondaryResourceId(request.getParameter("sid"));
        this.requestType = requestType;
        this.saveReferer();
    }

    /**
     * @return the request
     */
    @Override
    public HttpServletRequest getRequest() {
        return request;
    }

    /**
     * @param request the request to set
     */
    @Override
    public void setRequest(HttpServletRequest request) {
        this.request = request;
    }

    /**
     * @return the primaryResourceTypeName if it exists or an empty string
     */
    @Override
    public String getPrimaryResourceTypeName() {
        String result = (String) this.request.getAttribute("resource");
        if (result == null) {
            result = "";
        }
        return result;
    }

    /**
     * @param primaryResourceTypeName the primaryResourceTypeName to set
     */
    @Override
    public void setPrimaryResourceTypeName(String primaryResourceTypeName) {
        this.setAttribute("resource", primaryResourceTypeName);
    }

    /**
     * @return the primaryResourceTypeId
     */
    @Override
    public int getPrimaryResourceTypeId() {
        Integer value = (Integer) this.getAttribute("primaryResourceId");
        if (value == null) {
            value = 0;
        }
        return value.intValue();
    }

    /**
     * @param primaryResourceTypeId the primaryResourceTypeId to set
     */
    @Override
    public void setPrimaryResourceTypeId(String primaryResourceTypeId) {
        if (primaryResourceTypeId == null || primaryResourceTypeId.isEmpty() == true) {
            primaryResourceTypeId = "0";
        }
        this.setAttribute("primaryResourceId", Integer.parseInt(primaryResourceTypeId));
    }

    /**
     * @param primaryResourceTypeId the primaryResourceTypeId to set
     */
    @Override
    public void setPrimaryResourceTypeId(int primaryResourceTypeId) {
        this.setAttribute("primaryResourceId", primaryResourceTypeId);
    }

    /**
     * @return the primaryResourceId
     */
    @Override
    public int getPrimaryResourceId() {
        Integer value = (Integer) this.getAttribute("id");
        if (value == null) {
            value = 0;
        }
        return value.intValue();
    }

    /**
     * @param primaryResourceId the primaryResourceId to set
     */
    @Override
    public void setPrimaryResourceId(String primaryResourceId) {
        if (primaryResourceId == null || primaryResourceId.isEmpty() == true) {
            primaryResourceId = "0";
        }
        this.setAttribute("id", Integer.parseInt(primaryResourceId));
    }

    /**
     * @param primaryResourceId the primaryResourceId to set
     */
    @Override
    public void setPrimaryResourceId(int primaryResourceId) {
        this.setAttribute("id", primaryResourceId);
    }

    /**
     * @return the primaryActionTypeName if it exists or an empty string
     */
    @Override
    public String getPrimaryActionTypeName() {
        String result = (String) this.request.getAttribute("action");
        if (result == null) {
            result = "";
        }
        return result;
    }

    /**
     * @param primaryActionTypeName the primaryActionTypeName to set
     */
    @Override
    public void setPrimaryActionTypeName(String primaryActionTypeName) {
        this.setAttribute("action", primaryActionTypeName);
    }

    /**
     * @return the primaryActionTypeId
     */
    @Override
    public int getPrimaryActionTypeId() {
        Integer value = (Integer) this.getAttribute("primaryActionTypeId");
        if (value == null) {
            value = 0;
        }
        return value.intValue();
    }

    /**
     * @param primaryActionTypeId the primaryActionTypeId to set
     */
    @Override
    public void setPrimaryActionTypeId(String primaryActionTypeId) {
        if (primaryActionTypeId == null || primaryActionTypeId.isEmpty() == true) {
            primaryActionTypeId = "0";
        }
        this.setAttribute("primaryActionTypeId", Integer.parseInt(primaryActionTypeId));
    }

    /**
     * @param primaryActionTypeId the primaryActionTypeId to set
     */
    @Override
    public void setPrimaryActionTypeId(int primaryActionTypeId) {
        this.setAttribute("primaryActionTypeId", primaryActionTypeId);
    }

    /**
     * @return the secondaryResourceTypeName if it exists or an empty string
     */
    @Override
    public String getSecondaryResourceTypeName() {
        String result = (String) this.request.getAttribute("for");
        if (result == null) {
            result = "";
        }
        return result;
    }

    /**
     * @param secondaryResourceTypeName the secondaryResourceTypeName to set
     */
    @Override
    public void setSecondaryResourceTypeName(String secondaryResourceTypeName) {
        this.setAttribute("for", secondaryResourceTypeName);
    }

    /**
     * @return the secondaryResourceTypeId
     */
    @Override
    public int getSecondaryResourceTypeId() {
        Integer value = (Integer) this.getAttribute("secondaryResourceTypeId");
        if (value == null) {
            value = 0;
        }
        return value.intValue();
    }

    /**
     * @param secondaryResourceTypeId the secondaryResourceTypeId to set
     */
    @Override
    public void setSecondaryResourceTypeId(String secondaryResourceTypeId) {
        if (secondaryResourceTypeId == null || secondaryResourceTypeId.isEmpty() == true) {
            secondaryResourceTypeId = "0";
        }
        this.setAttribute("secondaryResourceTypeId", Integer.parseInt(secondaryResourceTypeId));
    }

    /**
     * @param secondaryResourceTypeId the secondaryResourceTypeId to set
     */
    @Override
    public void setSecondaryResourceTypeId(int secondaryResourceTypeId) {
        this.setAttribute("secondaryResourceTypeId", secondaryResourceTypeId);
    }

    /**
     * @return the secondaryResourceId
     */
    @Override
    public int getSecondaryResourceId() {
        Integer value = (Integer) this.getAttribute("sid");
        if (value == null) {
            value = 0;
        }
        return value.intValue();
    }

    /**
     * @param secondaryResourceId the secondaryResourceId to set
     */
    @Override
    public void setSecondaryResourceId(String secondaryResourceId) {
        if (secondaryResourceId == null || secondaryResourceId.isEmpty() == true) {
            secondaryResourceId = "0";
        }
        this.setAttribute("sid", Integer.parseInt(secondaryResourceId));
    }

    /**
     * @param secondaryResourceId the secondaryResourceId to set
     */
    @Override
    public void setSecondaryResourceId(int secondaryResourceId) {
        this.setAttribute("sid", secondaryResourceId);
    }

    /**
     * @return the secondaryActionTypeName if it exists or an empty string
     */
    @Override
    public String getSecondaryActionTypeName() {
        String result = (String) this.request.getAttribute("and");
        if (result == null) {
            result = "";
        }
        return result;
    }

    /**
     * @param secondaryActionTypeName the secondaryActionTypeName to set
     */
    @Override
    public void setSecondaryActionTypeName(String secondaryActionTypeName) {
        this.setAttribute("and", secondaryActionTypeName);
    }

    /**
     * @return the secondaryActionTypeId
     */
    @Override
    public int getSecondaryActionTypeId() {
        Integer value = (Integer) this.getAttribute("secondaryActionTypeId");
        if (value == null) {
            value = 0;
        }
        return value.intValue();
    }

    /**
     * @param secondaryActionTypeId the secondaryActionTypeId to set
     */
    @Override
    public void setSecondaryActionTypeId(String secondaryActionTypeId) {
        if (secondaryActionTypeId == null || secondaryActionTypeId.isEmpty() == true) {
            secondaryActionTypeId = "0";
        }
        this.setAttribute("secondaryActionTypeId", Integer.parseInt(secondaryActionTypeId));
    }

    /**
     * @param secondaryActionTypeId the secondaryActionTypeId to set
     */
    @Override
    public void setSecondaryActionTypeId(int secondaryActionTypeId) {
        this.setAttribute("secondaryActionTypeId", secondaryActionTypeId);
    }

    /**
     * Get the current request type to process the request object with.
     *
     * @return the currentRequestType as either POST or GET
     */
    @Override
    public String getMethod() {
        if (this.currentRequestType == null || this.currentRequestType.isEmpty()) {
            this.currentRequestType = this.request.getMethod();
        }
        return currentRequestType;
    }

    /**
     * Set the current request type that the request object should be processed
     * as.
     *
     * @param currentRequestType the currentRequestType to set as either POST or
     * GET
     */
    @Override
    public void setMethod(String currentRequestType) {
        this.currentRequestType = currentRequestType;
    }

    /**
     * Get a String value that maps to the parameter name stored in the request
     * object.
     *
     * @param attributeName name of the parameter to get
     * @return the parameter value if it exists or an empty string if it
     * doesn't.
     */
    @Override
    public String getParameter(String parameterName) {
        String param = this.request.getParameter(parameterName);
        if (param == null) {
            param = "";
        }
        return param;
    }

    /**
     * Get a String value that maps to the attribute name stored in the request
     * object.
     *
     * @param attributeName name of the attribute to get
     * @return the attribute value if it exists or an empty string if it
     * doesn't.
     */
    @Override
    public String getStringAttribute(String attributeName) {
        String attrib = (String) this.getAttribute(attributeName);
        if (attrib == null) {
            attrib = "";
        }
        return attrib;
    }

    /**
     * Get an integer value that maps to the attribute name stored in the
     * request object.
     *
     * @param attributeName name of the attribute to get
     * @return the attribute value
     */
    @Override
    public int getIntegerAttribute(String attributeName) {
        int attrib = (Integer) this.getAttribute(attributeName);
        return attrib;
    }

    /**
     * Get an object value that maps to the attribute name stored in the request
     * object.
     *
     * @param attributeName name of the attribute to get
     * @return the attribute object if it exists or null if it doesn't.
     */
    @Override
    public final Object getAttribute(String attributeName) {
        Object attrib = this.request.getAttribute(attributeName);
        //   System.out.println("Request.getAttribute() with param:" + attributeName + " returns: " + attrib);
        return attrib;
    }

    /**
     * Remove an object value that maps to the attribute name stored in the
     * request object.
     *
     * @param attributeName name of the attribute to remove
     */
    @Override
    public void removeAttribute(String attributeName) {
        this.request.removeAttribute(attributeName);
        //    System.out.println("Request.removeAttribute() with param:" + attributeName);
    }

    /**
     * Set an attribute object for the request object.
     *
     * @param key the name of the attribute to act as a key
     * @param attribute an attribute object
     */
    @Override
    public final void setAttribute(String key, Object attribute) {
        //     System.out.println("Request.setAttribute() with key:" + key + " and value:" + attribute);
        this.request.setAttribute(key, attribute);
    }

    /**
     * Get the form id / form name from the request object.
     *
     * @return the formId if one exists or an empty string
     */
    @Override
    public String getFormId() {
        String form = this.getStringAttribute("formSubmit");
        //     System.out.println("getStringAttribute Request.getFormId() returns: " + form);
        if (form == null || form.isEmpty() == true) {
            form = "";
        }
        //    System.out.println("Request.getFormId() returns: " + form);
        return form;
    }

    /**
     * Remove the form id / name from the request object
     */
    @Override
    public void removeFormId() {
        this.removeAttribute("formSubmit");
    }

    /**
     * Set the form Id / name.
     *
     * @param formId the formId to set
     */
    @Override
    public void setFormId(String formId) {
        this.setAttribute("formSubmit", formId);
    }

    /**
     * Set the request view used to display the result of the request to the
     * user.
     *
     * @param target a path indicating the request view such as "/index.jsp"
     */
    @Override
    public void setView(String target) {
        this.setAttribute("requestView", target);
    }

    /**
     * Get the request view used to display the request result to the user
     *
     * @return the request view or the default web view if none is set
     */
    @Override
    public String getView() {
        String view = this.getStringAttribute("requestView");
        if (view == null || view.isEmpty()) {
            view = "/index.jsp";
        }
        return view;
    }

    /**
     * Sets the result message for the request.
     *
     * @param msg the message to display on the request view for the user.
     */
    @Override
    public void setMessage(String msg) {
        this.setAttribute("requestResultMessage", msg);
    }

    /**
     * Gets the result message for for the request
     *
     * @return the result message if one exists or null
     */
    @Override
    public String getMessage() {
        String msg = this.getStringAttribute("requestResultMessage");
        return msg;
    }

    /**
     * Sets the type of result message. This in intended to help instruct the
     * view how to display the result message
     *
     * @param msgType names of the type of message such as success, forbidden,
     * error, default
     */
    @Override
    public void setMessageType(String msgType) {
        this.setAttribute("requestResultMessageType", msgType.toLowerCase());
    }

    /**
     * Gets the type of result message. This in intended to help instruct the
     * view how to display the result message
     *
     * @return msgType name of the type of message such as success, forbidden,
     * error, default
     */
    @Override
    public String getMessageType() {
        return this.getStringAttribute("requestResultMessageType");
    }

    /**
     * Get an object value that maps to the attribute name stored in the request
     * session object.
     *
     * @param attributeName name of the attribute to get
     * @return the attribute object if it exists or null if it doesn't.
     */
    @Override
    public Object getSessionAttribute(String attributeName) {
        Object attrib = this.request.getSession().getAttribute(attributeName);

        //     System.out.println("Request.getSessionAttribute() with param:" + attributeName + " returns: " + attrib);
        return attrib;
    }

    /**
     * Remove an object value that maps to the attribute name stored in the
     * request session object.
     *
     * @param attributeName name of the attribute to remove from the session
     */
    @Override
    public void removeSessionAttribute(String attributeName) {
        this.request.getSession().removeAttribute(attributeName);
    }

    /**
     * Set an attribute object for the request session object.
     *
     * @param key the name of the attribute to act as a key
     * @param attribute an attribute object
     */
    @Override
    public void setSessionAttribute(String key, Object attribute) {
        //      System.out.println("Request.setSessionAttribute() with key:" + key + " and value:" + attribute);
        this.request.getSession().setAttribute(key, attribute);
    }

    /**
     * This method saves the referer id from the request header and compiles the
     * patterns that are used to extract from the referer query string.
     */
    private void saveReferer() {
        this.referer = this.request.getHeader("Referer");
        //      System.out.println("Referer is:" + this.referer);
        this.actionPattern = Pattern.compile("\\baction=\\b(\\w+)\\b");
        this.resourcePattern = Pattern.compile("\\b&resource=\\b(\\w+)\\b");
        this.idPattern = Pattern.compile("\\b&id=\\b(\\d+)\\b");
        this.secondaryResourcePattern = Pattern.compile("\\b&for=\\b(\\w+)\\b");
    }

    /**
     * get the action from the referer url query
     *
     * @return the action if it is found or an empty string if not
     */
    @Override
    public String getRefererAction() {
        String action = "";
        Matcher matcher = this.actionPattern.matcher(this.referer);
        if (matcher.find() == true) {
            action = matcher.group(1);
        }
        //       System.out.println("getRefererAction is:" + action);
        return action;
    }

    /**
     * get the resource from the referer url query
     *
     * @return the resource if it is found or an empty string if not
     */
    @Override
    public String getRefererResource() {
        String res = "";
        Matcher matcher = this.resourcePattern.matcher(this.referer);
        if (matcher.find() == true) {
            res = matcher.group(1);
        }
        //      System.out.println("getRefererResource is:" + res);
        return res;
    }

    /**
     * get the id from the referer url query
     *
     * @return the id if it is found or an empty string if not
     */
    @Override
    public String getRefererId() {
        String id = "";
        Matcher matcher = this.idPattern.matcher(this.referer);
        if (matcher.find() == true) {
            id = matcher.group(1);
        }
        return id;
    }

    /**
     * get the secondary resource from the referer url query
     *
     * @return the resource if it is found or an empty string if not
     */
    @Override
    public String getRefererSecondaryResource() {
        String res = "";
        Matcher matcher = this.secondaryResourcePattern.matcher(this.referer);
        if (matcher.find() == true) {
            res = matcher.group(1);
        }
//        System.out.println("getRefererSecondaryResource is:" + res);
        return res;
    }

    /**
     * Get the current user from the session.
     *
     * @return the current user stored in the request session. If one doesn't
     * exist then return a "Guest" user
     */
    @Override
    public User getCurrentUser() {
        User user;
        if (this.getSessionAttribute("user") == null) {
            user = new User();// there's probably a better solution than hard-coding this here.
            user.setId(0);
            user.setRoleId(3);
            user.setName("Guest");
        } else {
            user = (User) this.getSessionAttribute("user");
        }
        return user;
    }

    /**
     * This method provides a standardized manner of setting the primary bean
     * that should be sent to the jsp. It is a wrapper removing the need for the
     * calling code to know the attribute name.
     *
     * @param bean the bean to set as the primary bean
     */
    @Override
    public void setBean(Object bean) {
        this.request.setAttribute("bean", bean);
    }

    /**
     * this method returns the primary bean.
     *
     * @return the primary bean if it exists or null if not
     */
    @Override
    public Object getBean() {
        Object bean = this.request.getAttribute("bean");
        return bean;
    }

    /**
     * This method provides a standardized manner of setting the primary bean
     * list that should be sent to the jsp. It is a wrapper removing the need
     * for the calling code to know the attribute name.
     *
     * @param beans the list of beans to set as the primary bean list
     */
    @Override
    public void setBeans(Object beans) {
        this.request.setAttribute("beans", beans);
    }

    /**
     * This method returns the primary list of beans. The calling code still
     * needs to cast to the proper bean list.
     *
     * @return the primary bean list if it exists or null if not.
     */
    @Override
    public Object getBeans() {
        Object beans = this.request.getAttribute("beans");
        return beans;
    }

    /**
     * This method returns a list of values for a parameter by converting the
     * String[] array from getParameterValues() to a List of Strings.
     *
     * @param parameterName the name of the parameter to retrieve the values
     * from.
     * @return a list of string values if they exist for the parameterName and
     * an empty list of Strings if not.
     */
    @Override
    public List<String> getParameters(String parameterName) {
        String[] paramsArray = this.request.getParameterValues(parameterName);
        List<String> paramsList;
        if (paramsArray != null && paramsArray.length > 0) {
            paramsList = new ArrayList<String>(Arrays.asList(paramsArray));
        } else {
            paramsList = new ArrayList<String>();
        }
        return paramsList;
    }

    /**
     * This method set's the request type for processing by internal code.
     *
     * @param rtype is ASYNC for AJAX requests and SYNC for default requests.
     */
    @Override
    public void setRequestType(RequestType rtype) {
        this.requestType = rtype;
    }

    /**
     * This method gets the current request type of the request.
     *
     * @return RequestType Enum of the current request.
     */
    @Override
    public RequestType getRequestType() {
        return this.requestType;
    }
}
